package com.gitee.wsl.compose.ui.toast

import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.AccessibilityManager
import com.gitee.wsl.ext.base.currentTimeMillis
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import kotlin.coroutines.resume
import kotlin.math.roundToLong

interface ToastData {
    val message: String
    val icon: ImageVector?
    val animationDuration: StateFlow<Int?>
    val type: ToastModel.Type?
    suspend fun run(accessibilityManager: AccessibilityManager?)
    fun pause()
    fun resume()
    fun dismiss()
    fun dismissed()

    companion object{
        fun create(message: String,
                   icon: ImageVector?,
                   continuation: CancellableContinuation<Unit>,
                   type: ToastModel.Type? = ToastModel.Type.Normal,
                   durationTime: Long? = null
                   ) = ToastDataImpl(message, icon, continuation, type,durationTime)
    }
}

@Stable
class ToastDataImpl(
    override val message: String,
    override val icon: ImageVector?,
    private val continuation: CancellableContinuation<Unit>,
    override val type: ToastModel.Type? = ToastModel.Type.Normal,
    val durationTime: Long? = null
) : ToastData {
    private var elapsed = 0L
    private var started = 0L
    private var duration = 0L
    private val _state = MutableStateFlow<Int?>(null)
    override val animationDuration: StateFlow<Int?> = _state.asStateFlow()

    val mDurationTime = durationTime ?: when (type) {
        ToastModel.Type.Normal -> ToastDurationTime
        ToastModel.Type.Success -> ToastDurationTime
        ToastModel.Type.Warning -> 3000
        ToastModel.Type.Error -> 5000
        else -> ToastDurationTime
    }

    override suspend fun run(accessibilityManager: AccessibilityManager?) {
        duration = durationTimeout(
            hasIcon = icon != null,
            accessibilityManager = accessibilityManager,
            timeout = mDurationTime
        )
        //无障碍决定永远显示
        //让我们等待明确的解雇，不要运行动画。
        if (duration == Long.MAX_VALUE) {
            delay(duration)
            return
        }

        resume()
        supervisorScope {
            launch {
                animationDuration.collectLatest { duration ->
                    val animationScale = coroutineContext.durationScale
                    if (duration != null) {
                        started = currentTimeMillis
                        // 关闭动画后，只需显示、等待和隐藏即可。
                        val finalDuration = when (animationScale) {
                            0f -> duration.toLong()
                            else -> (duration.toLong() * animationScale).roundToLong()
                        }
                        delay(finalDuration)
                        this@launch.cancel()
                    } else {
                        elapsed += currentTimeMillis - started
                        delay(Long.MAX_VALUE)
                    }
                }
            }
        }
    }

    override fun pause() {
        _state.value = null
    }

    override fun resume() {
        val remains = (duration - elapsed).toInt()
        if (remains > 0) {
            _state.value = remains
        } else {
            dismiss()
        }
    }

    override fun dismiss() {
        _state.value = 0
    }

    override fun dismissed() {
        if (continuation.isActive) {
            continuation.resume(Unit)
        }
    }
}

const val ToastDurationTime = 1666L
internal fun durationTimeout(
    hasIcon: Boolean,
    accessibilityManager: AccessibilityManager?,
    timeout: Long = ToastDurationTime
): Long {
    if (accessibilityManager == null) return timeout
    return accessibilityManager.calculateRecommendedTimeoutMillis(
        originalTimeoutMillis = timeout,
        containsIcons = hasIcon,
        containsText = true,
        containsControls = false,
    )
}